/**
 * @file app\protocol\mobus\protocol_mobus.c
 *
 * Copyright (C) 2023
 *
 * protocol_mobus.c is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * @author HinsShum hinsshum@qq.com
 *
 * @encoding utf-8
 */

/*---------- includes ----------*/
#include "protocol_mobus.h"
#include "checksum.h"
#include "options.h"
#include "storage.h"
#include <stdarg.h>
#include <string.h>

/*---------- macro ----------*/
#define TAG "PROTOCOL"
/*---------- type define ----------*/
/*---------- variable prototype ----------*/
/*---------- function prototype ----------*/
static void _send(const uint8_t* pbuf, uint32_t length);
static void _push(const uint8_t* pbuf, uint32_t length, uint32_t retrans);
static void _stop_retrans(void);
static bool _evt_cb(protocol_mobus_evt_t evt, mobus_ack_result_t* result);

/*---------- variable ----------*/
static struct protocol_mobus_ops ops = { .send = _send,
                                        .push = _push,
                                        .stop_retrans = _stop_retrans,
                                        .evt_cb = _evt_cb };
static uint16_t sync;
static uint8_t multicast_buffer[FIELD_SIZEOF(union protocol_mobus, group)];

/*---------- function ----------*/
static void _send(const uint8_t* pbuf, uint32_t length) {}

static void _push(const uint8_t* pbuf, uint32_t length, uint32_t retrans) {}

static void _stop_retrans(void) {}

static bool _evt_cb(protocol_mobus_evt_t evt, mobus_ack_result_t* result) {
    return false;
}

void protocol_mobus_init(struct protocol_mobus_ops* pops) {
    if (pops) {
        if (pops->send) {
            ops.send = pops->send;
        }
        if (pops->push) {
            ops.push = pops->push;
        }
        if (pops->stop_retrans) {
            ops.stop_retrans = pops->stop_retrans;
        }
        if (pops->evt_cb) {
            ops.evt_cb = pops->evt_cb;
        }
    }
}

void protocol_mobus_login_with_channel_type(
    uint8_t addr, uint8_t version, mobus_device_type_t type,
    uint32_t number_of_channels, mobus_device_type_t* channels_type) {
    uint8_t buf[FIELD_SIZEOF(union protocol_mobus, login) +
        CONFIG_STORAGE_MOBUS_CHANNEL_NUMS * 8 + 3] = { 0 };
    protocol_mobus_t protocol = (protocol_mobus_t)buf;
    assert(number_of_channels <= 8);
    assert(number_of_channels != 0);

    protocol->login.type = MOBUS_FRAME_TYPE_LOGIN;
    protocol->login.sync_hi = 0x00;
    protocol->login.dest_addr = 0x00;
    protocol->login.sync_lo = sync++;
    protocol->login.src_addr = addr;
    protocol->login.version = version;
    protocol->login.mobus_type_hi = (type >> 8) & 0xFF;
    protocol->login.mobus_type_lo = type & 0xFF;
    protocol->login.number_of_subs = number_of_channels & 0xF;
    protocol->login.sub_channel = 0x0;

    protocol->login.payload_length = CONFIG_STORAGE_MOBUS_CHANNEL_NUMS * 4 + 2;

    protocol->login.payload[0] = protocol->login.number_of_subs;
    protocol->login.payload[1] = 0x02; // 数据类型 扩展通道类型—面板、遥控器类

    protocol->login.payload[2] = 0x01;
    protocol->login.payload[3] = no_storage.temp_key.key_num;
    protocol->login.payload[4] = (type >> 8) & 0xFF;
    protocol->login.payload[5] = type & 0xFF;

    for (char i = 1; i < number_of_channels; i++) {
        protocol->login.payload[4 * (i - 1) + 6] = 0x01 << i;
        protocol->login.payload[4 * (i - 1) + 7] = 0x01;
        protocol->login.payload[4 * (i - 1) + 8] = (channels_type[i] >> 8) & 0xFF;
        protocol->login.payload[4 * (i - 1) + 9] = channels_type[i] & 0xFF;
    }

    protocol->login.payload[protocol->login.payload_length] =
        checksum_crc8_moorgen((void*)buf, sizeof(protocol->login) + protocol->login.payload_length);
    ops.push(buf, sizeof(protocol->login) + protocol->login.payload_length + 1, UINT32_MAX);
}

void protocol_mobus_login_with_channel_type_over_8_channles(
    uint8_t addr, uint8_t version, mobus_device_type_t type,
    uint16_t channel_nums, mobus_device_type_t* channels_type) {}

void protocol_mobus_ack(uint8_t addr, uint8_t sync_word,
    mobus_ack_result_t result) {
    uint8_t buf[FIELD_SIZEOF(union protocol_mobus, ack) + 2] = { 0 };
    protocol_mobus_t protocol = (protocol_mobus_t)buf;

    protocol->ack.type = MOBUS_FRAME_TYPE_ACK;
    protocol->ack.sync_hi = 0x00;
    protocol->ack.dest_addr = 0x00;
    protocol->ack.sync_lo = sync_word;
    protocol->ack.src_addr = addr;
    protocol->ack.cmd = 0x20;
    protocol->ack.payload_length = 0x01;
    protocol->ack.payload[0] = (uint8_t)result;
    protocol->ack.payload[protocol->ack.payload_length] = checksum_crc8_moorgen(buf, ARRAY_SIZE(buf) - 1);
    ops.send(buf, ARRAY_SIZE(buf));
}

static inline bool _is_ack(protocol_mobus_t protocol) {
    bool err = false;

    if (protocol->ack.type == MOBUS_FRAME_TYPE_ACK) {
        err = true;
    }

    return err;
}

static inline bool _ack_parse(protocol_mobus_t recv, protocol_mobus_t trans) {
    bool err = false;

    do {
        if (!trans) {
            break;
        }
        if (recv->ack.dest_addr != trans->common.src_addr) {
            break;
        }
        if (recv->ack.payload_length) {
            if (recv->ack.payload[0] == MOBUS_ACK_SYNC ||
                recv->ack.payload[0] == MOBUS_ACK_DATA_ERR) {
                sync = recv->ack.sync_lo;
            }
        }
        ops.stop_retrans();
        err = true;
    } while (0);

    return err;
}

static inline bool _is_broadcast(protocol_mobus_t protocol) {
    bool err = false;

    if (protocol->broadcast.type == MOBUS_FRAME_TYPE_BROADCAST) {
        err = true;
    }

    return err;
}

static inline bool _is_group(protocol_mobus_t protocol) {
    bool err = false;
    struct storage_mobus_device* modbus_devt = NULL;
    modbus_devt = storage_get(STORAGE_AREA_DEVICE);
    assert(modbus_devt);
    if (protocol->group.type < MOBUS_FRAME_TYPE_GROUP ||
        protocol->group.type > MOBUS_FRAME_TYPE_UPLOAD_GROUP_ADDR) {
        return err;
    }
    if (protocol->group.type == MOBUS_FRAME_TYPE_CONFIG_GROUP_ADDR)
    {
        if (protocol->common.dest_addr != modbus_devt->device.native_address)//check dev adress is right if err return err
            return err;
    }
    memcpy(multicast_buffer, (void*)protocol, ARRAY_SIZE(multicast_buffer));
    err = true;
    return err;
}

void protocol_mobus_parse(const uint8_t* recv_buf, uint32_t recv_length,
    const uint8_t* trans_buf, uint32_t trans_length) {
    protocol_mobus_t protocol = (protocol_mobus_t)recv_buf;
    struct protocol_mobus_evt evt = { 0 };
    mobus_ack_result_t result = MOBUS_ACK_DATA_ERR;

    do {
        if (recv_length <= FIELD_SIZEOF(union protocol_mobus, common)) {
            break;
        }
        if (recv_buf[recv_length - 1] !=
            checksum_crc8_moorgen((uint8_t*)recv_buf, recv_length - 1)) {
            xlog_tag_error(TAG, "rev frame crc is err\n");
            break;
        }
        if (_is_ack(protocol)) {
            if (_ack_parse((void*)recv_buf, (void*)trans_buf)) {
                evt.evt_type = PROTOCOL_MOBUS_EVT_TYPE_ACK;
                evt.frame_type = protocol->ack.type;
                evt.ack.send_frame_type = trans_buf[0];
                evt.ack.native_addr = protocol->ack.dest_addr;
                evt.ack.length = protocol->ack.payload_length;
                evt.ack.data = protocol->ack.payload;
                ops.evt_cb(&evt, &result);
            }
            break;
        }
        if (_is_broadcast(protocol)) {
            evt.evt_type = PROTOCOL_MOBUS_EVT_TYPE_BROADCAST;
            evt.frame_type = protocol->broadcast.type;
            evt.broadcast.cmd = protocol->broadcast.cmd;
            evt.broadcast.length = protocol->broadcast.payload_length;
            evt.broadcast.data = protocol->broadcast.payload;
            ops.evt_cb(&evt, &result);
            break;
        }
        if (_is_group(protocol)) {
            evt.evt_type = PROTOCOL_MOBUS_EVT_TYPE_GROUP;
            evt.frame_type = protocol->group.type;
            evt.multicast.length = protocol->group.payload_length;
            evt.multicast.data = protocol->group.payload;
            evt.multicast.uid = ((uint16_t)protocol->group.dest_orgroup_addr << 8) |
                protocol->group.src_orgroup_addr;
            ops.evt_cb(&evt, &result);
            break;
        }
        if (protocol->common.type == MOBUS_FRAME_TYPE_CONFIG_DEVICE &&
            protocol->common.dest_addr == 0xFF && protocol->common.src_addr == 0) {
            if (protocol->common.sync_hi == 0xFF) {
                evt.evt_type = PROTOCOL_MOBUS_EVT_TYPE_UNICAST;
                evt.frame_type = protocol->common.type;
                evt.unicast.native_addr = protocol->common.dest_addr;
                evt.unicast.sync = protocol->common.sync_lo;
                evt.unicast.number_of_channels = protocol->common.number_of_subs;
                evt.unicast.sub_channel = protocol->common.sub_channel;
                evt.unicast.length = protocol->common.payload_length;
                evt.unicast.data = protocol->common.payload;
                ops.evt_cb(&evt, &result);
                break;
            }
        }
        if (protocol->query.type == MOBUS_FRAME_TYPE_QUERY_STATUS) {
            evt.evt_type = PROTOCOL_MOBUS_EVT_TYPE_UNICAST;
            evt.frame_type = protocol->common.type;
            evt.unicast.native_addr = protocol->common.dest_addr;
            evt.unicast.sync = protocol->common.sync_lo;
            evt.unicast.number_of_channels = protocol->common.number_of_subs;
            evt.unicast.sub_channel = protocol->common.sub_channel;
            evt.unicast.length = protocol->common.payload_length;
            evt.unicast.cmd = protocol->common.sub_channel;
            evt.unicast.data = protocol->common.payload;
            ops.evt_cb(&evt, &result);break;
        }
        else {
            evt.evt_type = PROTOCOL_MOBUS_EVT_TYPE_UNICAST;
            evt.frame_type = protocol->common.type;
            evt.unicast.native_addr = protocol->common.dest_addr;
            evt.unicast.sync = protocol->common.sync_lo;
            evt.unicast.number_of_channels = protocol->common.number_of_subs;
            evt.unicast.sub_channel = protocol->common.sub_channel;
            evt.unicast.length = protocol->common.payload_length;
            evt.unicast.data = protocol->common.payload;
            if (evt.frame_type == MOBUS_FRAME_TYPE_QUERY_PARAMETERS && evt.unicast.length == 1)
                evt.unicast.cmd = MOBUS_CMD_QUERY_STATUS;
        }
        if (ops.evt_cb(&evt, &result)) {
            protocol_mobus_ack(protocol->common.dest_addr, protocol->common.sync_lo,
                result);
        }
    } while (0);
}

void protocol_mobus_upload(mobus_frame_type_t type, uint8_t addr,
    uint8_t number_of_channels, uint8_t sub_channel,
    const uint8_t* pbuf, uint8_t length) {
    uint32_t wanted_size =
        FIELD_SIZEOF(union protocol_mobus, common) + length + 1;
    protocol_mobus_t protocol = NULL;

    protocol = __malloc(wanted_size);
    if (protocol) {
        protocol->common.type = type;
        protocol->common.sync_hi = 0x00;
        protocol->common.dest_addr = 0x00;
        protocol->common.sync_lo = sync++;
        protocol->common.src_addr = addr;
        protocol->common.number_of_subs = number_of_channels & 0xF;
        protocol->common.sub_channel = sub_channel;
        protocol->common.payload_length = length;
        memcpy(protocol->common.payload, pbuf, length);
        protocol->common.payload[length] =
            checksum_crc8_moorgen((void*)protocol, wanted_size - 1);
        ops.push((void*)protocol, wanted_size, 4);
        __free(protocol);
    }
}
void protocol_mobus_dev_report(mobus_frame_type_t type, uint8_t sub_channel,
    const uint8_t* pbuf, uint8_t length) {
// 04 00 00 0B 90   52   03 01 00 00 55
// 04 00 00 07 90   10   02 01 01 9C
// 29 00 00 7B 12   20   03 10 01 01 B5
    struct storage_mobus_device* modbus_dev = NULL;
    unsigned char ket_id = 0;
    uint32_t wanted_size =
        FIELD_SIZEOF(union protocol_mobus, common) + length + 1;
    protocol_mobus_t protocol = NULL;
    modbus_dev = storage_get(STORAGE_AREA_DEVICE);
    assert(modbus_dev);
    protocol = __malloc(wanted_size);

    if (protocol) {
        protocol->common.type = type;
        protocol->common.sync_hi = 0x00;
        protocol->common.dest_addr = 0x00;
        protocol->common.sync_lo = sync++;
        protocol->common.src_addr = modbus_dev->device.native_address;
        protocol->common.number_of_subs = modbus_dev->device.channel_nums & 0xF;
        protocol->common.sub_channel = sub_channel;
        protocol->common.payload_length = length;
        memcpy(protocol->common.payload, pbuf, length);
        protocol->common.payload[length] =
            checksum_crc8_moorgen((void*)protocol, wanted_size - 1);
        ops.push((void*)protocol, wanted_size, 4);
        __free(protocol);
    }
}